/*
 * InvertedListFile.h
 *
 *  Created on: Nov 27, 2012
 *      Author: bruno
 */

#ifndef INVERTEDLISTFILE_H_
#define INVERTEDLISTFILE_H_

#include <cstring>
#include <memory>
#include "../common_components/FixedSizeBlockFile.h"
#include "../serializers/InvertedListRecordListSerializer.h"
#include "../serializers/InvertedListRecordSerializer.h"
#include "InvertedListRecord.h"
#include "InvertedList.h"

using std::unique_ptr;
using std::ios_base;

namespace physical
{

/**
 * Archivo para contener listas invertidas
 */
class InvertedListFile
{
public:

    /**
     * Constructor
     *
     * @param filename Nombre de archivo a asociar al InvertedListFile
     * @param trunc True para truncar el archivo, false en caso contrario.
     *               Valor por Defecto: False
     */
    InvertedListFile( const char* filename, bool trunc = false );

    /**
     * Destructor
     */
    virtual ~InvertedListFile();

    /**
     * Inserta una InvertedList en el archivo
     *
     * @param termID Id de término al que pertenece la InvertedList
     * @param invertedList InvertedList a agregar en el archivo
     * @return El bloque en el que se guardó la lista
     */
    size_t addList( term_id_t termID, InvertedList& invertedList );

    /**
     * Devuelve una InvertedList
     *
     * @param termID  Id de término al que pertenece la InvertedList
     * @param blockID Bloque en el que buscar la InvertedList
     * @return La InvertedList
     */
    InvertedList getList( term_id_t termID, size_t blockID );

    /**
     * Actualiza una lista invertida
     *
     * Si no hay capacidad en el bloque actual para guardar la lista
     *  actualizada, se re-ubica la lista en un nuevo bloque, esto se
     *  indica a travéz del valor de retorno que, si es igual al blockID
     *  pasado por parámetro entonces significa que no se re-ubicó la
     *  lista, si es distinto significa que la lista se re-ubico a ese
     *  nuevo bloque.
     *
     * @param termID  Id de término al que pertenece la InvertedList
     * @param blockID Bloque en el que buscar la InvertedList
     * @return El bloque en el que se guardó la lista actualizada
     */
    size_t updateList( term_id_t termID, size_t blockID,
            InvertedList& invertedList );

    /**
     * Remueve una lista invertida
     *
     * Remueve la lista invertida con id = termID
     *  del bloque blockID
     *
     * Si la lista invertida no se encuenta en el
     *  bloque especificado, lanza una excepción
     *
     * @throw InvertedListNotFoundException
     *
     * @param termID ID del término asociado a la lista invertida a eliminar
     * @param blockID Bloque del que eliminar la lista invertida
     */
    void removeList( term_id_t termID, size_t blockID );

protected:

    // Tamaño de bloque
    static const size_t s_blockSize = 128;

    // Archivo de bloques
    unique_ptr<FixedSizeBlockFile> m_file;

    // Serializador de registros internos ( InvertedListRecord )
    unique_ptr< Serializer<InvertedListRecord> > m_recordSerializer;

    // Serializador de Lista de registros internos
    unique_ptr< Serializer<InvertedListRecordList> > m_recordListSerializer;

    /*
     * Información de Control:
     *
     * Básicamente es un array de 128 bytes cada uno conteniendo
     *  la cantidad de bytes libres que hay en el bloque dado
     */
    unsigned char m_freeSpace[s_blockSize];

    size_t m_lastBlockWritten;

    /**
     * Devuelve un bloque con la capacidad indicada
     *
     * Devuelve el primer bloque que tenga una capacidad
     *  igual o superior a la indicada
     *
     * @param recordSize Capacidad del bloque
     * @return ID del bloque con capacidad igual o mayor, 0 si no
     *          bloques con dicha capacidad
     */
    size_t getBlockWithCapacity( size_t recordSize );

    /**
     * Inserta el registro en el bloque dado
     *
     * También actualiza información de control.
     * Si no hay suficiente espacio libre ene l bloque tira
     *  una excepción
     *
     * @throw NotEnoughFreeSpaceInBlockException
     *
     * @param blockID Bloque en el que se inserta el registro
     * @param record  Registro a insertar
     */
    void insertRecordInBlock( size_t blockID, InvertedListRecord& record );

    /**
     * Devuelve un InvertedListRecord guardado en un bloque
     *
     * Si el registro no se encuentra en el bloque lanza una excepción
     *
     * @throw InvertedListNotFoundInBlockException
     *
     * @param blockID ID del bloque en el que se encuentra el registro
     * @param termID ID del registro
     * @return El registro
     */
    InvertedListRecord getRecordFromBlock( size_t blockID, term_id_t termID );

    /**
     * Actualiza un InvertedListRecord en un bloque
     *
     * Si no hay más espacio libre en el bloque para actualizar
     *  el registro lo elimina del bloque y devuelve false.
     *
     * Si el registro no se encuentra en el bloque lanza una excepción
     *
     * También actualiza información de control
     *
     * @throw InvertedListNotFoundInBlockException
     *
     * @warning Si no se puede actualizar el registro por falta de espacio
     *           en el bloque, se elimina dicho registro del bloque para
     *           que pueda ser re-insertado en otro
     *
     * @param blockID ID del bloque en el que está el registro a actualizar
     * @param newRecord Nuevo registro con el que se reemplaza el anterior
     * @return true si se puso actualizar el registro, false en caso contrario
     */
    bool updateRecordInBlock( size_t blockID, InvertedListRecord& newRecord );

    /**
     * Elimina un Registro de un bloque
     *
     * También actualiza información de control
     *
     * @throw InvertedListNotFoundInBlockException
     *
     * @param blockID
     * @param termID
     */
    void removeRecordFromBlock( size_t blockID, term_id_t termID );

    /**
     * Guarda la información de control
     */
    void saveControlData();

    /**
     * Carga la información de control
     */
    void loadControlData();

    /**
     * Inicializa la información de control
     */
    void initializeControlData();

};

/*************************************************************
 **********************   Exepciones   **********************
 *************************************************************/

/**
 * Excepción Genérica de InvertedListFile
 */
class InvertedListFileException: public PhysicalException
{
};

/**
 * Excepción: La lista invertida no se encontró en el bloque indicado
 */
class InvertedListNotFoundInBlockException: public InvertedListFileException
{
    virtual const char* what() const throw ()
    {
        return "Inverted List not found in Block.";
    }
};

/**
 * Excepción: No hay suficiente espacio en el bloque para guardar
 *             la lista invertida indicada
 */
class NotEnoughFreeSpaceInBlockException: public InvertedListFileException
{
    virtual const char* what() const throw ()
    {
        return "Not enough Free Space in Block.";
    }
};

} /* namespace physical */
#endif /* INVERTEDLISTFILE_H_ */
